package pl.edu.mimuw.irs.rcp.controller.editor.workflow.command;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import pl.edu.mimuw.irs.core.model.design.net.Fragmentation;
import pl.edu.mimuw.irs.core.model.design.net.IRSNode;
import pl.edu.mimuw.irs.core.model.petrinet.Arc;
import pl.edu.mimuw.irs.core.model.petrinet.Net;

public class FragmentationCommand extends IRSNodeCommand {

	// *** Attributes
	
	private Fragmentation oldFragmentation;
	private List<IRSNode> oldChildren;
	private Fragmentation newFragmentation;
	private List<IRSNode> newChildren;
	
	// *** Constructors
	
	public FragmentationCommand(IRSNode node, Fragmentation fragmentation) {
		super(node);
		this.newFragmentation = fragmentation;
		this.oldChildren = new ArrayList<IRSNode>(node.getChildren());
		this.oldFragmentation = node.getFragmentation();
	}
	
	// *** Methods
	
	@Override
	public void performExecute() {
		if (newChildren == null) {
			getNode().setFragmentation(newFragmentation);
			newChildren = new ArrayList<IRSNode>(getNode().getChildren());
		} else {
			Set<Arc> restoredArcs = new LinkedHashSet<Arc>();
			restoreDetached(newChildren, restoredArcs);
			getNode().setChildren(newChildren, newFragmentation);
		}
	}
	
	@Override
	public void performUndo() {
		Set<Arc> restoredArcs = new LinkedHashSet<Arc>();
		restoreDetached(oldChildren, restoredArcs);
		getNode().setChildren(oldChildren, oldFragmentation);
	}
	
	private void restoreDetached(List<IRSNode> children, 
			Set<Arc> restoredArcs) {
		Net net = getNode().getNet();
		
		/*
		 * To restore whole group of nodes we have to add them back to the net, 
		 * as well as their arcs. It is important that during detach all children
		 * persisted and arcs were removed only from one side. However arc still 
		 * knows what Place and Transition it was connecting.
		 */
		for (IRSNode node : children) {
			if (!node.isDetached()) continue;
			node.setDetached(false);
			
			if (net != null) net.addNode(node);
			
			/* 
			 * We pass sets because we want to restore every arc exactly
			 * once.
			 */
			restoreArcs(node, restoredArcs);
			restoreDetached(node.getChildren(), restoredArcs);
		}
	}

	private void restoreArcs(IRSNode node,
			Set<Arc> restoredArcs) {
		
		for (Arc arc : node.getIncomingArcs()) {
			/*
			 * We skip arc if it has already been restored.
			 */
			if (restoredArcs.contains(arc)) continue;
			if (node.getNet() != null) node.getNet().addArc(arc);
			arc.getSource().addArc(arc);
			restoredArcs.add(arc);
		}
		for (Arc arc : node.getOutgoingArcs()) {
			if (restoredArcs.contains(arc)) continue;
			if (node.getNet() != null) node.getNet().addArc(arc);
			arc.getTarget().addArc(arc);
			restoredArcs.add(arc);
		}
	}
}
